/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System System Specific Module                         */
/*                                                                           */
/*    Copyright (C) 2010 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    netSNTP - Simple Network Time Protocol                                 */
/*****************************************************************************/

/*****************************************************************************/
/* Includes                                                                  */
/*****************************************************************************/
#include <krnlMemory.h>
#include <krnlDateTime.h>
#include <krnlSystemTimer.h>
#include <krnlRealTimeClock.h>
#include <netPacketBuffer.h>
#include <netSocket.h>
#include <netSNTP.h>
#include <netIP.h>
#include <netDNS.h>

/*****************************************************************************/
/* Constants                                                                 */
/*****************************************************************************/
#define NTP_EPOCH            (86400U * (365U * 70U + 17U))
#define NTP_PORT             123
#define NTP_REPLY_TIMEOUT    6000
#define NTP_PACKET_LENGTH    (12*4)

/*****************************************************************************/
/* Types                                                                     */
/*****************************************************************************/

// State machine for a DNS query
typedef enum
{
   SNTP_IDLE = 0,             // Initial state to reset client state variables
   SNTP_DNS_RESOLUTION,				// DNS resolution in progress
	 SNTP_TIME_QUERY,						// Time query must be sent
   SNTP_WAIT_FOR_RESPONSE,    // Wait for response from DNS server
   SNTP_FAIL,									// Opertion timeout
   SNTP_DONE									// SNTP query is finished
} netSNTPState;

/*****************************************************************************/
/* Local function prototypes                                                 */
/*****************************************************************************/
static void netSNTPProcess(void);

/*****************************************************************************/
/* Module local variables                                                    */
/*****************************************************************************/
static netSNTPState l_state = SNTP_IDLE;
static IN_ADDR l_server_address;
static SOCKET l_socket = INVALID_SOCKET;
static dosDateTime l_datetime;
static dosBool l_time_valid = dosFalse;
static dosWord l_time_stamp = 0;

/*****************************************************************************/
/* SNTP functions                                                            */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! Opens SNTP client and starts date/time query
//! \param Host name of the NTP	time server
//! \return dosTrue if query procedure is started, dosFalse when SNTP is already opened
dosBool netSNTPOpenHostName( dosString in_host_name )
{
	// sanity check
	if( l_state != SNTP_IDLE )
		return dosFalse;

	// initialize time query
	l_time_valid = dosFalse;

	// start DNS name resolution	
	if( netDNSOpen(in_host_name) )
		l_state = SNTP_DNS_RESOLUTION;
	else
		l_state = SNTP_FAIL;

	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Opens SNTP client and starts date/time query
//! \param NTP timer server IP address
//! \return dosTrue if query procedure is started, dosFalse when SNTP is already opened
dosBool netSNTPOpenIPAddress( IN_ADDR* in_address )
{
	// sanity check
	if( l_state != SNTP_IDLE )
		return dosFalse;

	// initialize time query
	l_time_valid = dosFalse;
	l_server_address = *in_address;
	l_state = SNTP_TIME_QUERY;

	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Returns SNTP process status (and calls worker function)
//! \param none
//! \return dosTrue if resolver is busy
dosBool netSNTPIsBusy(void)
{
	// call resolver process if needed
	if( l_state != SNTP_DONE && l_state != SNTP_IDLE )
	{
		netSNTPProcess();
		return dosTrue;
	}
	else
		return dosFalse;
}

///////////////////////////////////////////////////////////////////////////////
//! Closes SNTP process
void netSNTPClose(void)
{
	// DNS procedure
	if( l_state == SNTP_DNS_RESOLUTION )
	{
		netDNSClose();
	}

	// close socket
	if( l_socket != INVALID_SOCKET )
	{
		closesocket( l_socket);
		l_socket = INVALID_SOCKET;
	}

	// reset state
	l_state = SNTP_IDLE;
}

///////////////////////////////////////////////////////////////////////////////
//! Returns time received from the NTP server
//! \param Pointer receiving time/date
//! \return True if time/date is valid
dosBool netSNTPGetTime( dosDateTime* out_time )
{
	if( l_time_valid )
		*out_time = l_datetime;
	
	return l_time_valid;
}

/*****************************************************************************/
/* Local functions                                                           */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
//! SNTP worker function
static void netSNTPProcess(void)
{
  dosByte buffer[NTP_PACKET_LENGTH];

	switch( l_state )
	{
    // wait for DNS resolution
    case SNTP_DNS_RESOLUTION:
      if( !netDNSIsBusy() )
      {
        // get resolved address
        if( netDNSGetResolvedIPAddress( &l_server_address ) )
        {
          l_state = SNTP_TIME_QUERY;
        }
        else
        {
          l_state = SNTP_FAIL;
        }

        // close DNS
        netDNSClose();
      }
      break;

		// start time query
		case SNTP_TIME_QUERY:
		{
			struct sockaddr_in socket_address;
			int address_length = sizeof(socket_address);
			dosDateTime timestamp;

			// open socket
			if( (l_socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == SOCKET_ERROR )
			{
				l_state = SNTP_FAIL;
				break;
			}
			
			// connect socket
      socket_address.sin_family = AF_INET;
      socket_address.sin_addr   = l_server_address;
      socket_address.sin_port   = NTP_PORT;
		  if( connect(l_socket, (struct sockaddr*)&socket_address, sizeof(socket_address)) == SOCKET_ERROR )
		  {
				l_state = SNTP_FAIL;
				break;
			}

			// prepare query
			krnlGetDateTime( &timestamp );

			// create NTP packet
			krnlMemZero( buffer, NTP_PACKET_LENGTH );
			buffer[0] = (3 << 3) + 3; // version (3) and mode (3)
			*(dosDWord*)(buffer+24) =	htonl(krnlConvertDateTimeToSeconds( &timestamp ) + NTP_EPOCH);

      // store timestamp
      l_time_stamp = krnlGetSystemTimer();

			// send message
			if( send( l_socket, buffer, NTP_PACKET_LENGTH, 0) == SOCKET_ERROR )
				l_state = SNTP_FAIL;

			// next stage
			l_state = SNTP_WAIT_FOR_RESPONSE;
		}
		break;

		// waiting for SNTP response
		case SNTP_WAIT_FOR_RESPONSE:
			// check for timeout
			if( krnlGetTimeSince(l_time_stamp) > NTP_REPLY_TIMEOUT )
			{
				l_state = SNTP_FAIL;
				break;
			}

			if( recvfrom( l_socket, buffer, 0, NTP_PACKET_LENGTH, DOS_NULL, DOS_NULL) == NTP_PACKET_LENGTH )
			{
				// close socket
				closesocket(l_socket);

				// convert server transmit timestamp to date/time
				krnlConvertSecondsToDateTime( &l_datetime, *(dosDWord*)(buffer+40) - NTP_EPOCH );

				// change status to success
				l_time_valid = dosTrue;
				l_state = SNTP_DONE;
		  }
			break;
	}
}

/*
static struct ntp_server ntp_servers[MAX_NTP_SERVERS];
static int num_ntp_servers;

int sntp_get(struct ntp_server *srv, struct timeval *tv)
{
  struct ntp_packet pkt;
  int s;
  int rc;
  int timeout;

  //syslog(LOG_AUX, "sntp_get: retrieving time from %a", &srv->sa.sin_addr);

  s = socket(AF_INET, SOCK_DGRAM, 0);
  if (s < 0) return s;

  rc = connect(s, (struct sockaddr *) &srv->sa, sizeof(struct sockaddr_in));
  if (rc < 0) 
  {
    close(s);
    return rc;
  }

  memset(&pkt, 0, sizeof pkt);
  pkt.vn = 4;
  pkt.mode = 3;
  pkt.originate_timestamp_secs = htonl(time(0) + NTP_EPOCH);

  rc = send(s, &pkt, sizeof pkt, 0);
  if (rc != sizeof pkt)
  {
    close(s);
    return rc;
  }

  timeout = NTP_REPLY_TIMEOUT;
  setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(int)); 

  rc = recvfrom(s, &pkt, sizeof pkt, 0, NULL, NULL);
  if (rc != sizeof pkt)
  {
    close(s);
    return rc;
  }

  tv->tv_sec = ntohl(pkt.transmit_timestamp_secs) - NTP_EPOCH;
  tv->tv_usec = ntohl(pkt.transmit_timestamp_fraq) / 4295;

  close(s);
  return 0;
} */
                  /*
void __stdcall sntpd(void *arg)
{
  int i, j;
  struct ntp_server *srv;
  struct hostent *hp;
  int success;
  struct timeval tv;

  //syslog(LOG_AUX, "sntpd: started");
  while (1)
  {
    success = 0;
    for (i = 0; i < num_ntp_servers; i++)
    {
      srv = &ntp_servers[i];

      if (srv->hostname != NULL)
      {
        hp = gethostbyname(srv->hostname);
        if (hp)
        {
          for (j = 0; hp->h_addr_list[j] != NULL; j++) 
          {
            struct in_addr *addr = (struct in_addr *) (hp->h_addr_list[j]);
            memcpy(&srv->sa.sin_addr, addr, hp->h_length);
            if (sntp_get(srv, &tv) >= 0)
            {
              success = 1;
              break;
            }
          }
        }
      }

			else
      {
        if (sntp_get(srv, &tv) >= 0) success = 1;
      }

      if (success) break;
    }

    if (success)
    {
      //struct timeval now;
      //gettimeofday(&now);
      settimeofday(&tv);
      //syslog(LOG_AUX, "sntpd: adjusting %d %d %a", tv.tv_sec - now.tv_sec, tv.tv_usec - now.tv_usec, &srv->sa.sin_addr);
      msleep(TIME_ADJUST_INTERVAL);
    }
    else
    {
      syslog(LOG_AUX, "sntpd: error obtaining time from time server");
      msleep(TIME_ADJUST_RETRY);
    }
  }
}

void init_sntpd()
{
  int idx;
  struct section *sect;
  struct property *prop;
  
  if (peb->ipaddr.s_addr == INADDR_ANY) return;

  idx = 0;
  if (peb->ntp_server1.s_addr != INADDR_ANY)
  {
    ntp_servers[idx].hostname = NULL;
    ntp_servers[idx].sa.sin_addr.s_addr = peb->ntp_server1.s_addr;
    ntp_servers[idx].sa.sin_family = AF_INET;
    ntp_servers[idx].sa.sin_port = htons(NTP_PORT);
    idx++;
  }

  if (peb->ntp_server2.s_addr != INADDR_ANY)
  {
    ntp_servers[idx].hostname = NULL;
    ntp_servers[idx].sa.sin_addr.s_addr = peb->ntp_server2.s_addr;
    ntp_servers[idx].sa.sin_family = AF_INET;
    ntp_servers[idx].sa.sin_port = htons(NTP_PORT);
    idx++;
  }

  sect = find_section(osconfig, "ntp");
  if (sect)
  {
    prop = sect->properties;
    while (prop)
    {
      if (idx == MAX_NTP_SERVERS) break;

      ntp_servers[idx].hostname = prop->name;
      ntp_servers[idx].sa.sin_addr.s_addr = INADDR_ANY;
      ntp_servers[idx].sa.sin_family = AF_INET;
      ntp_servers[idx].sa.sin_port = htons(NTP_PORT);

      idx++;
      prop = prop->next;
    }
  }

  num_ntp_servers = idx;
  if (idx > 0) beginthread(sntpd, 0, NULL, 0, NULL);
}
*/


/*
struct ntp_server
{
  struct sockaddr_in sa;
  char *hostname;
};

struct ntp_packet
{
  unsigned char mode : 3;
  unsigned char vn : 3;
  unsigned char li : 2;
  unsigned char stratum;
  char poll;
  char precision;
  unsigned long root_delay;
  unsigned long root_dispersion;
  unsigned long reference_identifier;
  unsigned long reference_timestamp_secs;
  unsigned long reference_timestamp_fraq;
  unsigned long originate_timestamp_secs;
  unsigned long originate_timestamp_fraq;
  unsigned long receive_timestamp_seqs;
  unsigned long receive_timestamp_fraq;
  unsigned long transmit_timestamp_secs;
  unsigned long transmit_timestamp_fraq;
};
  */
